import { LoadingManager, Raycaster, Skeleton, Vector2 } from "three";
import { GLTFLoader } from "../Libs/GLTFLoader";
import { KTX2Loader } from "../Libs/KTX2Loader";

import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";

// import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
// import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";

import { MeshoptDecoder } from "../Libs/meshopt_decoder.module";
// import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
import { gsap } from "gsap";

export default class Util {
  static loadManager = new LoadingManager();
  static ktx2Loader = new KTX2Loader();
  static raycaster = new Raycaster();
  static pointer = new Vector2();

  private static _gltfLoader: GLTFLoader;
  public static get gltfLoader(): GLTFLoader {
    if (!this._gltfLoader) {
      Util.ktx2Loader.setTranscoderPath("assest/libs/basis/");
      Util.ktx2Loader.setWorkerLimit(1);

      this._gltfLoader = new GLTFLoader(this.loadManager);
      this._gltfLoader.setMeshoptDecoder(MeshoptDecoder).setKTX2Loader(Util.ktx2Loader);
    }
    return this._gltfLoader;
  }
  public static set gltfLoader(v: GLTFLoader) {
    this._gltfLoader = v;
  }
}

export function NewID() {
  return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
      v = c == "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

export function RandomFromInterval(min: number, max: number, minus: boolean = false) {
  let n = Math.random() * (max - min) + min;
  if (minus) return Math.random() < 0.5 ? -n : n;
  return n;
}

export function toAsync(targets: gsap.TweenTarget, vars: gsap.TweenVars): Promise<gsap.core.Tween> {
  return new Promise<gsap.core.Tween>((resolve, _reject) => {
    vars.onComplete = (function (func) {
      return function () {
        func && func();
        resolve(this);
      };
    })(vars.onComplete);
    gsap.to(targets, vars);
  });
}

export function CloneGLTF(gltf: any): GLTF {
  const clone = {
    animations: gltf.animations,
    scene: gltf.scene.clone(true),
  };

  const skinnedMeshes = {};

  gltf.scene.traverse((node) => {
    if (node.isSkinnedMesh) {
      skinnedMeshes[node.name] = node;
    }
  });

  const cloneBones = {};
  const cloneSkinnedMeshes = {};

  clone.scene.traverse((node) => {
    if (node.isBone) {
      cloneBones[node.name] = node;
    }

    if (node.isSkinnedMesh) {
      cloneSkinnedMeshes[node.name] = node;
    }
  });

  for (let name in skinnedMeshes) {
    const skinnedMesh = skinnedMeshes[name];
    const skeleton = skinnedMesh.skeleton;
    const cloneSkinnedMesh = cloneSkinnedMeshes[name];

    const orderedCloneBones = [];

    for (let i = 0; i < skeleton.bones.length; ++i) {
      const cloneBone = cloneBones[skeleton.bones[i].name];
      orderedCloneBones.push(cloneBone);
    }

    cloneSkinnedMesh.bind(new Skeleton(orderedCloneBones, skeleton.boneInverses), cloneSkinnedMesh.matrixWorld);
  }

  return clone as any;
}

let MaxDistance = 8;

export { MaxDistance };
